#include "jni.h"
#include "jawt.h"
#include "jawt_md.h"
#include "DShow.h"
#include "main.h"
#include "VideoFilter.h"
#include <map>
using namespace std;

#define SAFE_RELEASE(h) if(h){h->Release();h=NULL;}

#define ShowError(hwnd, hr) PrintError(hwnd, hr, __LINE__)

void PrintError(HWND hwnd, HRESULT hr, int line)
{
	char szErr[MAX_ERROR_TEXT_LEN];
	DWORD res = AMGetErrorTextA(hr, szErr, MAX_ERROR_TEXT_LEN);
	char title[50];
	sprintf_s(title, sizeof(title), "DirectShow Error(line:%d)", line);
	if (!res)
	{
		sprintf_s(szErr, sizeof(szErr), "Unkown error");
	}
	MessageBoxA(hwnd, szErr, title, 0);
}

class DeviceData
{
public:
	IBaseFilter *pSrcFilter;

	ICaptureGraphBuilder2 *pBuild;
	IGraphBuilder *pGraph;
	IMediaControl *pControl;
	IBaseFilter* pVmr;
	IVMRWindowlessControl* pWc;
	IVMRFilterConfig* pConfig;
	IBaseFilter* pVideoFilter;

	HWND hwnd;

	DeviceData()
	{
		ZeroMemory(this, sizeof(DeviceData));
	}

	void Release()
	{
		if (pControl)
		{
			pControl->Stop();
		}
		SAFE_RELEASE(pBuild)
		SAFE_RELEASE(pGraph)
		SAFE_RELEASE(pControl)
		SAFE_RELEASE(pVmr)
		SAFE_RELEASE(pWc)
		SAFE_RELEASE(pConfig)
		SAFE_RELEASE(pVideoFilter)
	}
};

map<IBaseFilter*, DeviceData> deviceDataMap;

JNIDLLAPI(enumDevices, jobject)(JNIEnv *env, jclass)
{
	static HRESULT CoInitializeHR = CoInitialize(NULL);

	jclass clsArrayList = env->FindClass("java/util/ArrayList");
	jmethodID ctrArrayList = env->GetMethodID(clsArrayList, "<init>", "()V");
	jmethodID addArrayList = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");

	jclass clsCaptureDevice = env->FindClass("kobetool/dshowwrapper/CaptureDevice");
	jmethodID ctrCaptureDevice = env->GetMethodID(clsCaptureDevice, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");

	jobject arrayList = env->NewObject(clsArrayList, ctrArrayList);

	ICreateDevEnum *pDevEnum = NULL;
	IEnumMoniker *pEnum = NULL;

	// Create the System Device Enumerator.
	HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
		CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, 
		reinterpret_cast<void**>(&pDevEnum));
	if (FAILED(hr))
	{
		return arrayList;
	}
	// Create an enumerator for the video capture category.
	hr = pDevEnum->CreateClassEnumerator(
		CLSID_VideoInputDeviceCategory,
		&pEnum, 0);
	if (FAILED(hr))
	{
		pDevEnum->Release();
		return arrayList;
	}
	if (pEnum)
	{
		IMoniker *pMoniker = NULL;
		while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
		{
			IPropertyBag *pPropBag;
			hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
				(void**)(&pPropBag));
			if (FAILED(hr))
			{
				pMoniker->Release();
				continue;  // Skip this one, maybe the next one will work.
			}
			
			jstring friendlyName = NULL;
			jstring description = NULL;
			jstring devicePath = NULL;
			IBaseFilter *pSrcFilter = NULL;

			// Find the description or friendly name.
			VARIANT varName;
			VariantInit(&varName);
			hr = pPropBag->Read(L"Description", &varName, 0);
			if (SUCCEEDED(hr))
			{
				description = env->NewString((jchar*)varName.bstrVal, (jsize)wcslen(varName.bstrVal));
				VariantClear(&varName);
			}
			hr = pPropBag->Read(L"FriendlyName", &varName, 0);
			if (SUCCEEDED(hr))
			{
				friendlyName = env->NewString((jchar*)varName.bstrVal, (jsize)wcslen(varName.bstrVal));
				VariantClear(&varName);
			}
			hr = pPropBag->Read(L"DevicePath", &varName, 0);
			if (SUCCEEDED(hr))
			{
				devicePath = env->NewString((jchar*)varName.bstrVal, (jsize)wcslen(varName.bstrVal));
				VariantClear(&varName);
			}
			
			hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pSrcFilter);
			if (SUCCEEDED(hr))
			{
				jobject capDeivce = env->NewObject(clsCaptureDevice, ctrCaptureDevice, friendlyName, description, devicePath, pSrcFilter);
				env->CallBooleanMethod(arrayList, addArrayList, capDeivce);
			}

			pPropBag->Release();
			pMoniker->Release();
		}
		pEnum->Release();
	}

	return arrayList;
}

HWND getHwnd(JNIEnv *env, jobject device)
{
	HWND hwnd = NULL;
	JAWT awt;
	JAWT_DrawingSurface* ds;
	JAWT_DrawingSurfaceInfo* dsi;
	JAWT_Win32DrawingSurfaceInfo* dsi_win;
	jboolean result;
	jint lock;

	// Get the AWT
	awt.version = JAWT_VERSION_1_3;
	result = JAWT_GetAWT(env, &awt);

	if (result!=JNI_FALSE)
	{
		// Get the drawing surface
		ds = awt.GetDrawingSurface(env, device);
		if (ds!=NULL)
		{
			// Lock the drawing surface
			lock = ds->Lock(ds);
			if((lock & JAWT_LOCK_ERROR) == 0)
			{
				// Get the drawing surface info
				dsi = ds->GetDrawingSurfaceInfo(ds);

				// Get the platform-specific drawing info
				dsi_win = (JAWT_Win32DrawingSurfaceInfo*)dsi->platformInfo;

				hwnd = dsi_win->hwnd;

				// Free the drawing surface info
				ds->FreeDrawingSurfaceInfo(dsi);

				// Unlock the drawing surface
				ds->Unlock(ds);
			}
			// Free the drawing surface
			awt.FreeDrawingSurface(ds);
		}
	}

	return hwnd;
}

void paintBlack(JNIEnv *env, jobject device, HWND hwnd = NULL)
{
	if (!hwnd)
	{	
		hwnd = getHwnd(env, device);
		if(!hwnd)
		{
			return;
		}
	}
	RECT rect;
	if(GetClientRect(hwnd, &rect))
	{
		HDC hdc = GetDC(hwnd);
		if (hdc)
		{
			SelectObject(hdc, GetStockObject(BLACK_BRUSH));
			Rectangle(hdc, 0, 0, rect.right, rect.bottom);
			ReleaseDC(hwnd, hdc);
		}
	}
}

DeviceData* findDeviceData(JNIEnv *env, jobject device)
{
	jclass clsCaptureDevice = env->FindClass("kobetool/dshowwrapper/CaptureDevice");
	jfieldID pSrcFilterCaptureDevice = env->GetFieldID(clsCaptureDevice, "pSrcFilter", "I");

	IBaseFilter *pSrcFilter = (IBaseFilter*)env->GetIntField(device, pSrcFilterCaptureDevice);
	if (!pSrcFilter)
	{
		return NULL;
	}
	map<IBaseFilter*, DeviceData>::iterator it = deviceDataMap.find(pSrcFilter);
	if (it==deviceDataMap.end())
	{
		DeviceData deviceData;
		deviceData.pSrcFilter = pSrcFilter;
		deviceData.hwnd = getHwnd(env, device);
		if(!deviceData.hwnd)
		{
			return NULL;
		}
		deviceDataMap.insert(pair<IBaseFilter*, DeviceData>(pSrcFilter, deviceData));
		return &deviceDataMap.find(pSrcFilter)->second;
	}
	else
	{
		return &it->second;
	}
}

JNIDLLAPI(paint, void)(JNIEnv *env, jobject device, jobject)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (deviceData)
	{
		if (deviceData->pGraph)
		{
			HDC hdc = GetDC(deviceData->hwnd);
			deviceData->pWc->RepaintVideo(deviceData->hwnd, hdc);
			ReleaseDC(deviceData->hwnd, hdc);
		}
		else
		{
			paintBlack(env, device, deviceData->hwnd);
		}
	}
}

JNIDLLAPI(destroy, jboolean)(JNIEnv *env, jobject device)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (deviceData)
	{
		IBaseFilter *pSrcFilter = deviceData->pSrcFilter;
		deviceData->Release();
		deviceDataMap.erase(deviceDataMap.find(deviceData->pSrcFilter));
		pSrcFilter->Release();

		jclass clsCaptureDevice = env->FindClass("kobetool/dshowwrapper/CaptureDevice");
		jfieldID pSrcFilterCaptureDevice = env->GetFieldID(clsCaptureDevice, "pSrcFilter", "I");

		env->SetIntField(device, pSrcFilterCaptureDevice, 0);
		return true;
	}
	else
	{
		return false;
	}
}

void ResizeCanvas(DeviceData *deviceData, int width, int height)
{
	if (deviceData && deviceData->pWc)
	{
		RECT srcRect, dstRect;
		srcRect.left = srcRect.top = 0;
		deviceData->pWc->GetNativeVideoSize(&srcRect.right, &srcRect.bottom, NULL, NULL);
		dstRect.left = dstRect.top = 0;
		dstRect.right = width;
		dstRect.bottom = height;
		deviceData->pWc->SetVideoPosition(&srcRect, &dstRect);
	}
}

JNIDLLAPI(start, jboolean)(JNIEnv *env, jobject device, jobject videoFormat, jboolean hFlip)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (!deviceData)
	{
		return false;
	}

	if (deviceData->pVmr)
	{
		return true;
	}

	HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, 
		CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&deviceData->pBuild);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		return false;
	}

	hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
		CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&deviceData->pGraph);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pGraph->AddFilter(deviceData->pSrcFilter, NULL);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pBuild->SetFiltergraph(deviceData->pGraph);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	// Create the VMR. 
	hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, 
		CLSCTX_INPROC, IID_IBaseFilter, (void**)&deviceData->pVmr); 
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	// Add the VMR to the filter graph.
	hr = deviceData->pGraph->AddFilter(deviceData->pVmr, NULL); 
	if (FAILED(hr)) 
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}
	// Set the rendering mode.  
	
	hr = deviceData->pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&deviceData->pConfig);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pConfig->SetRenderingMode(VMRMode_Windowless);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&deviceData->pWc);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pWc->SetVideoClippingWindow(deviceData->hwnd);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	if (videoFormat)
	{
		jclass clsVideoFormat = env->FindClass("kobetool/dshowwrapper/VideoFormat");
		jmethodID getWidthVideoFormat = env->GetMethodID(clsVideoFormat, "getWidth", "()I");
		jmethodID getHeightVideoFormat = env->GetMethodID(clsVideoFormat, "getHeight", "()I");
		jmethodID getSubTypeVideoFormat = env->GetMethodID(clsVideoFormat, "getSubType", "()Lkobetool/dshowwrapper/GUID;");

		jclass clsGUID = env->FindClass("kobetool/dshowwrapper/GUID");
		jmethodID getBufGUID = env->GetMethodID(clsGUID, "getBuf", "()Ljava/nio/ByteBuffer;");

		IAMStreamConfig *pConfig = NULL;
		hr = deviceData->pBuild->FindInterface(
			&PIN_CATEGORY_CAPTURE,
			&MEDIATYPE_Video,
			deviceData->pSrcFilter,
			IID_IAMStreamConfig, (void**)&pConfig);
		if (FAILED(hr))
		{
			ShowError(deviceData->hwnd, hr);
			deviceData->Release();
			return false;
		}
		
		int iCount = 0, iSize = 0;
		hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);

		if (iSize!=sizeof(VIDEO_STREAM_CONFIG_CAPS))
		{
			ShowError(deviceData->hwnd, E_FAIL);
			pConfig->Release();
			deviceData->Release();
			return false;
		}
		
		bool formatok = false;

		for (int iFormat=0; iFormat < iCount; iFormat++)
		{
			VIDEO_STREAM_CONFIG_CAPS scc;
			AM_MEDIA_TYPE *pmtConfig;
			hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
			if (SUCCEEDED(hr))
			{
				/* Examine the format, and possibly use it. */
				if (pmtConfig->formattype==FORMAT_VideoInfo)
				{
					jobject guid = env->CallObjectMethod(videoFormat, getSubTypeVideoFormat);
					if (guid)
					{
						jobject guidBuf = env->CallObjectMethod(guid, getBufGUID);
						void *buf = env->GetDirectBufferAddress(guidBuf);
						if(pmtConfig->subtype != *((GUID*)buf))
						{
							DeleteMediaType(pmtConfig);
							continue;
						}
					}
					jint width = env->CallIntMethod(videoFormat, getWidthVideoFormat);
					if (width)
					{
						VIDEOINFOHEADER *pHeader = (VIDEOINFOHEADER*)pmtConfig->pbFormat;
						if (pHeader->bmiHeader.biWidth!=width)
						{
							DeleteMediaType(pmtConfig);
							continue;
						}
					}
					jint height = env->CallIntMethod(videoFormat, getHeightVideoFormat);
					if (height)
					{
						VIDEOINFOHEADER *pHeader = (VIDEOINFOHEADER*)pmtConfig->pbFormat;
						if (pHeader->bmiHeader.biHeight!=height)
						{
							DeleteMediaType(pmtConfig);
							continue;
						}
					}
					hr = pConfig->SetFormat(pmtConfig);
					DeleteMediaType(pmtConfig);
					if (FAILED(hr))
					{
						continue;
					}
					formatok = true;
					break;
				}
				// Delete the media type when you are done.
				DeleteMediaType(pmtConfig);
			}
		}

		pConfig->Release();

		if (!formatok)
		{
			deviceData->Release();
			return false;
		}
	}

	deviceData->pVideoFilter = new VideoFilter(&hr, hFlip==JNI_TRUE);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pGraph->AddFilter(deviceData->pVideoFilter, L"VideoFilter");
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	IPin *pOutPin, *pInPin;
	hr = deviceData->pBuild->FindPin(deviceData->pSrcFilter, PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, NULL, FALSE, 0, &pOutPin);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pBuild->FindPin(deviceData->pVideoFilter, PINDIR_INPUT, NULL, NULL, FALSE, 0, &pInPin);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		pOutPin->Release();
		deviceData->Release();
		return false;
	}

	hr = deviceData->pGraph->ConnectDirect(pOutPin, pInPin, NULL);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		pOutPin->Release();
		pInPin->Release();
		deviceData->Release();
		return false;
	}

	SAFE_RELEASE(pOutPin)
	SAFE_RELEASE(pInPin)

	hr = deviceData->pBuild->RenderStream(NULL, &MEDIATYPE_Video, 
		deviceData->pVideoFilter, NULL, deviceData->pVmr);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		pOutPin->Release();
		deviceData->Release();
		return false;
	}

	hr = deviceData->pGraph->QueryInterface(IID_IMediaControl, (void**)&deviceData->pControl);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}

	hr = deviceData->pControl->Run();
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		deviceData->Release();
		return false;
	}
	RECT rect;
	GetClientRect(deviceData->hwnd, &rect);
	ResizeCanvas(deviceData, rect.right - rect.left, rect.bottom - rect.top);

	return true;
}

JNIDLLAPI(setBounds, void)(JNIEnv *env, jobject device, jint x, jint y, jint width, jint height)
{
	ResizeCanvas(findDeviceData(env, device), width, height);
	jclass clsCanvas = env->FindClass("java/awt/Canvas");
	jmethodID setBoundsCanvas = env->GetMethodID(clsCanvas, "setBounds", "(IIII)V");
	env->CallNonvirtualObjectMethod(device, clsCanvas, setBoundsCanvas, x, y, width, height);
}

JNIDLLAPI(stop, jboolean)(JNIEnv *env, jobject device)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (deviceData)
	{
		deviceData->Release();
		return true;
	}
	else
	{
		return false;
	}
}

JNIDLLAPI(isRunning, jboolean)(JNIEnv *env, jobject device)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (deviceData)
	{
		return deviceData->pVmr!=NULL;
	}
	else
	{
		return false;
	}
}

JNIDLLAPI(getFormat, jobject)(JNIEnv *env, jobject device)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (!deviceData || !deviceData->pVideoFilter)
	{
		return NULL;
	}

	jclass clsVideoFormat = env->FindClass("kobetool/dshowwrapper/VideoFormat");
	jmethodID ctrVideoFormat = env->GetMethodID(clsVideoFormat, "<init>", "(IILkobetool/dshowwrapper/GUID;)V");

	jclass clsGUID = env->FindClass("kobetool/dshowwrapper/GUID");
	jmethodID ctrGUID = env->GetMethodID(clsGUID, "<init>", "(Ljava/nio/ByteBuffer;)V");

	VideoFilter *pVideoFilter = (VideoFilter*)deviceData->pVideoFilter;
	

	void *buf = malloc(sizeof(GUID));
	*((GUID*)buf) = pVideoFilter->GetVideoType();
	jobject byteBuf = env->NewDirectByteBuffer(buf, sizeof(GUID));
	jobject guid = env->NewObject(clsGUID, ctrGUID, byteBuf);
	jobject fmt = env->NewObject(clsVideoFormat, ctrVideoFormat, 
		pVideoFilter->GetVideoWidth(), pVideoFilter->GetVideoHeight(), guid);

	return fmt;
}

JNIDLLAPI(queryFormats, jobject)(JNIEnv *env, jobject device)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (!deviceData)
	{
		return NULL;
	}

	jclass clsArrayList = env->FindClass("java/util/ArrayList");
	jmethodID ctrArrayList = env->GetMethodID(clsArrayList, "<init>", "()V");
	jmethodID addArrayList = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");

	jclass clsVideoFormat = env->FindClass("kobetool/dshowwrapper/VideoFormat");
	jmethodID ctrVideoFormat = env->GetMethodID(clsVideoFormat, "<init>", "(IILkobetool/dshowwrapper/GUID;)V");

	jclass clsGUID = env->FindClass("kobetool/dshowwrapper/GUID");
	jmethodID ctrGUID = env->GetMethodID(clsGUID, "<init>", "(Ljava/nio/ByteBuffer;)V");

	jobject fmtList = env->NewObject(clsArrayList, ctrArrayList);

	IAMStreamConfig *pConfig = NULL;

	boolean isRunning = deviceData->pBuild!=NULL;
	if (!isRunning)
	{
		HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, 
			CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&deviceData->pBuild);
		if (FAILED(hr))
		{
			ShowError(deviceData->hwnd, hr);
			return NULL;
		}

		hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
			CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&deviceData->pGraph);
		if (FAILED(hr))
		{
			ShowError(deviceData->hwnd, hr);
			deviceData->Release();
			return NULL;
		}

		hr = deviceData->pGraph->AddFilter(deviceData->pSrcFilter, NULL);
		if (FAILED(hr))
		{
			ShowError(deviceData->hwnd, hr);
			deviceData->Release();
			return NULL;
		}

		hr = deviceData->pBuild->SetFiltergraph(deviceData->pGraph);
		if (FAILED(hr))
		{
			ShowError(deviceData->hwnd, hr);
			deviceData->Release();
			return NULL;
		}
	}
	
	HRESULT hr = deviceData->pBuild->FindInterface(
		&PIN_CATEGORY_CAPTURE,
		&MEDIATYPE_Video,
		deviceData->pSrcFilter,
		IID_IAMStreamConfig, (void**)&pConfig);
	if (FAILED(hr))
	{
		ShowError(deviceData->hwnd, hr);
		if (!isRunning)
		{
			deviceData->Release();
		}
		return NULL;
	}

	int iCount = 0, iSize = 0;
	hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);

	if (iSize!=sizeof(VIDEO_STREAM_CONFIG_CAPS))
	{
		ShowError(deviceData->hwnd, E_FAIL);
		if (!isRunning)
		{
			deviceData->Release();
		}
		return NULL;
	}

	for (int iFormat=0; iFormat < iCount; iFormat++)
	{
		VIDEO_STREAM_CONFIG_CAPS scc;
		AM_MEDIA_TYPE *pmtConfig;
		hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
		if (SUCCEEDED(hr))
		{
			/* Examine the format, and possibly use it. */
			if (pmtConfig->formattype==FORMAT_VideoInfo)
			{
				VIDEOINFOHEADER *pHeader = (VIDEOINFOHEADER*)pmtConfig->pbFormat;

				void *buf = malloc(sizeof(GUID));
				*((GUID*)buf) = pmtConfig->subtype;
				jobject byteBuf = env->NewDirectByteBuffer(buf, sizeof(GUID));
				jobject guid = env->NewObject(clsGUID, ctrGUID, byteBuf);
				jobject fmt = env->NewObject(clsVideoFormat, ctrVideoFormat, 
					pHeader->bmiHeader.biWidth, pHeader->bmiHeader.biHeight, guid);

				env->CallBooleanMethod(fmtList, addArrayList, fmt);
			}
			// Delete the media type when you are done.
			DeleteMediaType(pmtConfig);
		}
	}

	SAFE_RELEASE(pConfig)
	if (!isRunning)
	{
		deviceData->Release();
	}
	return fmtList;
}

JNIDLLAPI(grab, jobject)(JNIEnv *env, jobject device, jobject buffer)
{
	DeviceData *deviceData = findDeviceData(env, device);
	if (!deviceData || !deviceData->pVideoFilter)
	{
		return NULL;
	}
	void *pBuf = NULL;
	int bufLen = 0;
	if (buffer)
	{
		pBuf = env->GetDirectBufferAddress(buffer);
		bufLen = (int)env->GetDirectBufferCapacity(buffer);
	}
	VideoFilter *pVideoFilter = (VideoFilter*)deviceData->pVideoFilter;
	pVideoFilter->StartQueryBuffer((BYTE*)pBuf, bufLen);
	int nBufLen;
	for (;;)
	{
		
		if ((pBuf = pVideoFilter->GetBuffer(nBufLen))==NULL)
		{
			Sleep(10);
			continue;
		}
		break;
	}
	if (nBufLen!=bufLen)
	{
		buffer = env->NewDirectByteBuffer(pBuf, nBufLen);
	}
	return buffer;
}